﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.Objects;
using System.Data;

namespace Repositories.Implementations
{
  /// <summary>
  /// The class use applying unit of work pattern
  /// </summary>
  public class UnitOfWork : IDisposable
  {
    private DbTransaction _transaction;
    private ObjectContext _objectContext;
    private bool _disposed;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    /// <param name="context">The context.</param>
    public UnitOfWork( ObjectContext context )
    {
      _objectContext = context;
    }

    public bool IsInTransaction
    {
      get
      {
        return _transaction != null;
      }
    }

    /// <summary>
    /// Begins the transaction.
    /// </summary>
    public void BeginTransaction()
    {
      BeginTransaction( IsolationLevel.ReadCommitted );
    }

    /// <summary>
    /// Begins the transaction.
    /// </summary>
    /// <param name="isolationLevel">The isolation level.</param>
    public void BeginTransaction( IsolationLevel isolationLevel )
    {
      if( _transaction != null )
      {
        throw new ApplicationException( "Cannot begin a new transaction while an existing transaction is still running. " +
                                        "Please commit or rollback the existing transaction before starting a new one." );
      }
      OpenConnection();
      _transaction = _objectContext.Connection.BeginTransaction( isolationLevel );
    }

    /// <summary>
    /// Rolls the back transaction.
    /// </summary>
    public void RollBackTransaction()
    {
      if( _transaction == null )
      {
        throw new ApplicationException( "Cannot roll back a transaction while there is no transaction running." );
      }

      try
      {
        _transaction.Rollback();
      }
      catch
      {
        throw;
      }
      finally
      {
        ReleaseCurrentTransaction();
      }
    }

    /// <summary>
    /// Commits the transaction.
    /// </summary>
    public void CommitTransaction()
    {
      if( _transaction == null )
      {
        throw new ApplicationException( "Cannot roll back a transaction while there is no transaction running." );
      }

      try
      {
        _objectContext.SaveChanges();
        _transaction.Commit();
      }
      catch
      {
        _transaction.Rollback();
        throw;
      }
      finally
      {
        ReleaseCurrentTransaction();
      }
    }

    /// <summary>
    /// Saves the changes.
    /// </summary>
    public void SaveChanges()
    {
      if( IsInTransaction )
      {
        throw new ApplicationException( "A transaction is running. Call BeginTransaction instead." );
      }
      _objectContext.SaveChanges();
    }

    public void AcceptAllChanges()
    {
      if( IsInTransaction )
      {
        throw new ApplicationException( "A transaction is running. Call BeginTransaction instead." );
      }
      _objectContext.AcceptAllChanges();
    }

    /// <summary>
    /// Releases the current transaction
    /// </summary>
    private void ReleaseCurrentTransaction()
    {
      if( _transaction != null )
      {
        _transaction.Dispose();
        _transaction = null;
      }
    }

    private void OpenConnection()
    {
      if( _objectContext.Connection.State != ConnectionState.Open )
      {
        _objectContext.Connection.Open();
      }
    }

    #region Implementation of IDisposable

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
      Dispose( true );
      GC.SuppressFinalize( this );
    }

    /// <summary>
    /// Disposes the managed and unmanaged resources.
    /// </summary>
    /// <param name="disposing"></param>
    private void Dispose( bool disposing )
    {
      if( !disposing )
        return;

      if( _disposed )
        return;

      ReleaseCurrentTransaction();

      _disposed = true;
    }
    

    #endregion
  }

}
